// Copyright Epic Games, Inc. All Rights Reserved.

#include "LyraSettingValueDiscrete_Resolution.h"

#include "Framework/Application/SlateApplication.h"
#include "GameFramework/GameUserSettings.h"
#include "RHI.h"
#include "UnrealEngine.h"

#include UE_INLINE_GENERATED_CPP_BY_NAME(LyraSettingValueDiscrete_Resolution)

#define LOCTEXT_NAMESPACE "LyraSettings"

/**
 * 构造函数
 */
ULyraSettingValueDiscrete_Resolution::ULyraSettingValueDiscrete_Resolution()
{
}

/**
 * 初始化时调用
 */
void ULyraSettingValueDiscrete_Resolution::OnInitialized()
{
	Super::OnInitialized(); // 调用父类初始化方法

	InitializeResolutions(); // 初始化分辨率
}

/**
 * 存储初始设置
 */
void ULyraSettingValueDiscrete_Resolution::StoreInitial()
{
	// 忽略 // 空实现
}

/**
 * 重置为默认值
 */
void ULyraSettingValueDiscrete_Resolution::ResetToDefault()
{
	// 忽略 // 空实现
}

/**
 * 恢复到初始值
 */
void ULyraSettingValueDiscrete_Resolution::RestoreToInitial()
{
	// 忽略 // 空实现
}

/**
 * 根据索引设置离散选项
 */
void ULyraSettingValueDiscrete_Resolution::SetDiscreteOptionByIndex(int32 Index)
{
	if (Resolutions.IsValidIndex(Index) && Resolutions[Index].IsValid()) // 如果分辨率索引有效
	{
		GEngine->GetGameUserSettings()->SetScreenResolution(Resolutions[Index]->GetResolution()); // 设置屏幕分辨率
		NotifySettingChanged(EGameSettingChangeReason::Change); // 通知设置已更改
	}
}

/**
 * 获取离散选项索引
 */
int32 ULyraSettingValueDiscrete_Resolution::GetDiscreteOptionIndex() const
{
	const UGameUserSettings* UserSettings = CastChecked<const UGameUserSettings>(GEngine->GetGameUserSettings()); // 获取游戏用户设置

	return FindIndexOfDisplayResolutionForceValid(UserSettings->GetScreenResolution()); // 返回强制有效的显示分辨率索引
}

/**
 * 获取离散选项
 */
TArray<FText> ULyraSettingValueDiscrete_Resolution::GetDiscreteOptions() const
{
	TArray<FText> ReturnResolutionTexts; // 返回分辨率文本数组

	for (int32 i = 0; i < Resolutions.Num(); ++i) // 遍历分辨率
	{
		ReturnResolutionTexts.Add(Resolutions[i]->GetDisplayText()); // 添加显示文本
	}

	return ReturnResolutionTexts; // 返回分辨率文本数组
}

/**
 * 依赖项更改时调用
 */
void ULyraSettingValueDiscrete_Resolution::OnDependencyChanged()
{
	const FIntPoint CurrentResolution = GEngine->GetGameUserSettings()->GetScreenResolution(); // 获取当前分辨率
	SelectAppropriateResolutions(); // 选择适当的分辨率
	SetDiscreteOptionByIndex(FindClosestResolutionIndex(CurrentResolution)); // 根据最接近的分辨率索引设置离散选项
}

/**
 * 初始化分辨率
 */
void ULyraSettingValueDiscrete_Resolution::InitializeResolutions()
{
	Resolutions.Empty(); // 清空分辨率数组
	ResolutionsFullscreen.Empty(); // 清空全屏分辨率数组
	ResolutionsWindowed.Empty(); // 清空窗口化分辨率数组
	ResolutionsWindowedFullscreen.Empty(); // 清空窗口化全屏分辨率数组

	FDisplayMetrics InitialDisplayMetrics; // 初始显示指标
	FSlateApplication::Get().GetInitialDisplayMetrics(InitialDisplayMetrics); // 获取初始显示指标

	FScreenResolutionArray ResArray; // 屏幕分辨率数组
	RHIGetAvailableResolutions(ResArray, true); // 获取可用分辨率

	// 确定可用的窗口化模式
	{
		TArray<FIntPoint> WindowedResolutions; // 窗口化分辨率数组
		const FIntPoint MinResolution(1280, 720); // 最小分辨率
		// 使用主显示分辨率减1以从列表中排除主显示分辨率。
		// 这是为了避免创建过大的窗口，导致部分游戏超出屏幕而无法更改分辨率。
		const FIntPoint MaxResolution(InitialDisplayMetrics.PrimaryDisplayWidth - 1, InitialDisplayMetrics.PrimaryDisplayHeight - 1); // 最大分辨率
		// 排除4:3及以下
		const float MinAspectRatio = 16 / 10.f; // 最小宽高比

		if (MaxResolution.X >= MinResolution.X && MaxResolution.Y >= MinResolution.Y) // 如果最大分辨率大于等于最小分辨率
		{
			GetStandardWindowResolutions(MinResolution, MaxResolution, MinAspectRatio, WindowedResolutions); // 获取标准窗口分辨率
		}

		if (GSystemResolution.WindowMode == EWindowMode::Windowed) // 如果当前窗口模式为窗口化
		{
			WindowedResolutions.AddUnique(FIntPoint(GSystemResolution.ResX, GSystemResolution.ResY)); // 添加唯一分辨率
			WindowedResolutions.Sort([](const FIntPoint& A, const FIntPoint& B) { return A.X != B.X ? A.X < B.X : A.Y < B.Y; }); // 排序分辨率
		}

		// 如果没有标准分辨率。添加主显示大小，以便至少存在一个。
		// 这可能发生在我们在非标准设备上运行时。
		if (WindowedResolutions.Num() == 0) // 如果窗口化分辨率数量为0
		{
			WindowedResolutions.Add(FIntPoint(InitialDisplayMetrics.PrimaryDisplayWidth, InitialDisplayMetrics.PrimaryDisplayHeight)); // 添加主显示分辨率
		}

		ResolutionsWindowed.Empty(WindowedResolutions.Num()); // 清空窗口化分辨率数组
		for (const FIntPoint& Res : WindowedResolutions) // 遍历窗口化分辨率
		{
			TSharedRef<FScreenResolutionEntry> Entry = MakeShared<FScreenResolutionEntry>(); // 创建屏幕分辨率条目
			Entry->Width = Res.X; // 设置宽度
			Entry->Height = Res.Y; // 设置高度

			ResolutionsWindowed.Add(Entry); // 添加到窗口化分辨率数组
		}
	}

	// 确定可用的窗口化全屏模式
	{
		FScreenResolutionRHI* RHIInitialResolution = ResArray.FindByPredicate([InitialDisplayMetrics](const FScreenResolutionRHI& ScreenRes) { // 查找初始分辨率
			return ScreenRes.Width == InitialDisplayMetrics.PrimaryDisplayWidth && ScreenRes.Height == InitialDisplayMetrics.PrimaryDisplayHeight; // 匹配宽度和高度
		});

		TSharedRef<FScreenResolutionEntry> Entry = MakeShared<FScreenResolutionEntry>(); // 创建屏幕分辨率条目
		if (RHIInitialResolution) // 如果找到初始分辨率
		{
			// 如果在官方列表中，使用该值
			Entry->Width = RHIInitialResolution->Width; // 设置宽度
			Entry->Height = RHIInitialResolution->Height; // 设置高度
			Entry->RefreshRate = RHIInitialResolution->RefreshRate; // 设置刷新率
		}
		else
		{
			// RHI不期望的自定义分辨率
			Entry->Width = InitialDisplayMetrics.PrimaryDisplayWidth; // 设置宽度为主显示宽度
			Entry->Height = InitialDisplayMetrics.PrimaryDisplayHeight; // 设置高度为主显示高度

			// TODO: 不确定如何计算刷新率
			Entry->RefreshRate = FPlatformMisc::GetMaxRefreshRate(); // 设置刷新率为最大刷新率
		}

		ResolutionsWindowedFullscreen.Add(Entry); // 添加到窗口化全屏分辨率数组
	}

	// 确定可用的全屏模式
	if (ResArray.Num() > 0) // 如果分辨率数组不为空
	{
		// 先尝试更严格的条件，然后尝试更宽松的条件，我们希望至少保留一个分辨率
		for (int32 FilterThreshold = 0; FilterThreshold < 3; ++FilterThreshold) // 遍历过滤阈值
		{
			for (int32 ModeIndex = 0; ModeIndex < ResArray.Num(); ModeIndex++) // 遍历分辨率数组
			{
				const FScreenResolutionRHI& ScreenRes = ResArray[ModeIndex]; // 获取屏幕分辨率

				// 先尝试严格测试，然后尝试宽松测试
				if (ShouldAllowFullScreenResolution(ScreenRes, FilterThreshold)) // 检查是否允许全屏分辨率
				{
					TSharedRef<FScreenResolutionEntry> Entry = MakeShared<FScreenResolutionEntry>(); // 创建屏幕分辨率条目
					Entry->Width = ScreenRes.Width; // 设置宽度
					Entry->Height = ScreenRes.Height; // 设置高度
					Entry->RefreshRate = ScreenRes.RefreshRate; // 设置刷新率

					ResolutionsFullscreen.Add(Entry); // 添加到全屏分辨率数组
				}
			}

			if (ResolutionsFullscreen.Num()) // 如果全屏分辨率数组不为空
			{
				// 我们找到了一些分辨率，否则我们尝试更宽松的条件
				break; // 跳出循环
			}
		}
	}

	SelectAppropriateResolutions(); // 选择适当的分辨率
}

/**
 * 选择适当的分辨率
 */
void ULyraSettingValueDiscrete_Resolution::SelectAppropriateResolutions()
{
	EWindowMode::Type const WindowMode = GEngine->GetGameUserSettings()->GetFullscreenMode(); // 获取全屏模式
	if (LastWindowMode != WindowMode) // 如果最后窗口模式不等于当前窗口模式
	{
		LastWindowMode = WindowMode; // 更新最后窗口模式

		Resolutions.Empty(); // 清空分辨率数组
		switch (WindowMode) // 根据窗口模式切换
		{
		case EWindowMode::Windowed: // 窗口化模式
			Resolutions.Append(ResolutionsWindowed); // 附加窗口化分辨率
			break;
		case EWindowMode::WindowedFullscreen: // 窗口化全屏模式
			Resolutions.Append(ResolutionsWindowedFullscreen); // 附加窗口化全屏分辨率
			break;
		case EWindowMode::Fullscreen: // 全屏模式
			Resolutions.Append(ResolutionsFullscreen); // 附加全屏分辨率
			break;
		}

		NotifyEditConditionsChanged(); // 通知编辑条件已更改
	}
}

/**
 * 过滤掉奇怪的分辨率，以便UI和测试问题较少。这是游戏特定的。
 * @param ScreenRes 分辨率
 * @param FilterThreshold 0/1/2 以确保我们至少获得一些分辨率（UI可能有问题，但至少我们获得一些分辨率条目）
 */
bool ULyraSettingValueDiscrete_Resolution::ShouldAllowFullScreenResolution(const FScreenResolutionRHI& SrcScreenRes, int32 FilterThreshold) const
{
	FScreenResolutionRHI ScreenRes = SrcScreenRes; // 复制屏幕分辨率

	// 预期：4:3=1.333, 16:9=1.777, 16:10=1.6, 多显示器宽屏：>2
	bool bIsPortrait = ScreenRes.Width < ScreenRes.Height; // 是否为竖屏
	float AspectRatio = (float)ScreenRes.Width / (float)ScreenRes.Height; // 计算宽高比

	// 如果是竖屏，将值翻转回横屏，这样我们就不需要特殊处理下面的所有测试
	if (bIsPortrait) // 如果是竖屏
	{
		AspectRatio = 1.0f / AspectRatio; // 翻转宽高比
		ScreenRes.Width = SrcScreenRes.Height; // 交换宽度和高度
		ScreenRes.Height = SrcScreenRes.Width; // 交换高度和宽度
	}

	// 过滤掉与主显示器原生宽高比不匹配的分辨率
	// TODO: 其他游戏允许用户选择游戏全屏的显示器。这将允许
	// 当用户的显示器类型不同时，此过滤是正确的！目前，游戏可以基于其他因素更改
	// 使用的显示器（最大窗口重叠等），因此我们可能最终选择目标显示器不支持的分辨率。
	if (FilterThreshold < 1) // 如果过滤阈值小于1
	{
		FDisplayMetrics DisplayMetrics; // 显示指标
		FSlateApplication::Get().GetInitialDisplayMetrics(DisplayMetrics); // 获取初始显示指标

		// 默认显示宽高比为所需宽高比，以防此平台无法提供信息。强制接受此分辨率。
		float DisplayAspect = AspectRatio; // 默认显示宽高比

		// 某些平台可能无法检测到显示设备的原生分辨率，因此在这种情况下不要过滤
		for (int32 MonitorIndex = 0; MonitorIndex < DisplayMetrics.MonitorInfo.Num(); ++MonitorIndex) // 遍历显示器信息
		{
			FMonitorInfo& MonitorInfo = DisplayMetrics.MonitorInfo[MonitorIndex]; // 获取显示器信息

			if (MonitorInfo.bIsPrimary) // 如果是主显示器
			{
				DisplayAspect = (float)MonitorInfo.NativeWidth / (float)MonitorInfo.NativeHeight; // 计算显示宽高比
				break; // 跳出循环
			}
		}

		// 如果宽高比不完全相等，拒绝
		if (FMath::Abs(DisplayAspect - AspectRatio) > KINDA_SMALL_NUMBER) // 如果宽高比差异大于微小值
		{
			return false; // 返回不允许
		}
	}

	// 更宽松的测试具有更大的FilterThreshold

	// 最小为1280x720
	if (FilterThreshold < 2 && (ScreenRes.Width < 1280 || ScreenRes.Height < 720)) // 如果过滤阈值小于2且分辨率太小
	{
		// 过滤掉太小的分辨率
		return false; // 返回不允许
	}

	return true; // 返回允许
}

/**
 * 查找显示分辨率的索引
 */
int32 ULyraSettingValueDiscrete_Resolution::FindIndexOfDisplayResolution(const FIntPoint& InPoint) const
{
	// 查找当前分辨率
	for (int32 i = 0, Num = Resolutions.Num(); i < Num; ++i) // 遍历分辨率
	{
		if (Resolutions[i]->GetResolution() == InPoint) // 如果分辨率匹配
		{
			return i; // 返回索引
		}
	}

	return INDEX_NONE; // 返回无效索引
}

/**
 * 强制查找显示分辨率的索引
 */
int32 ULyraSettingValueDiscrete_Resolution::FindIndexOfDisplayResolutionForceValid(const FIntPoint& InPoint) const
{
	int32 Result = FindIndexOfDisplayResolution(InPoint); // 查找显示分辨率的索引
	if (Result == INDEX_NONE && Resolutions.Num() > 0) // 如果未找到索引且分辨率数组不为空
	{
		Result = Resolutions.Num() - 1; // 返回最后一个索引
	}

	return Result; // 返回结果
}

/**
 * 查找最接近的分辨率索引
 */
int32 ULyraSettingValueDiscrete_Resolution::FindClosestResolutionIndex(const FIntPoint& Resolution) const
{	
	int32 Index = 0; // 索引
	int32 LastDiff = Resolution.SizeSquared(); // 最后差异

	for (int32 i = 0, Num = Resolutions.Num(); i < Num; ++i) // 遍历分辨率
	{
		// 我们比较平方对角线
		int32 Diff = FMath::Abs(Resolution.SizeSquared() - Resolutions[i]->GetResolution().SizeSquared()); // 计算差异
		if (Diff <= LastDiff) // 如果差异小于等于最后差异
		{				
			Index = i; // 更新索引
		}
		LastDiff = Diff; // 更新最后差异
	}

	return Index; // 返回索引
}

/**
 * 获取标准窗口分辨率
 */
void ULyraSettingValueDiscrete_Resolution::GetStandardWindowResolutions(const FIntPoint& MinResolution, const FIntPoint& MaxResolution, float MinAspectRatio, TArray<FIntPoint>& OutResolutions)
{
	static TArray<FIntPoint> StandardResolutions; // 静态标准分辨率数组
	if (StandardResolutions.Num() == 0) // 如果标准分辨率数组为空
	{
		// 维基百科提供的标准分辨率（http://en.wikipedia.org/wiki/Graphics_display_resolution）

		// 扩展图形阵列
		{
			new(StandardResolutions) FIntPoint(1024, 768); // XGA

														   // WXGA（3个版本）
			new(StandardResolutions) FIntPoint(1366, 768); // FWXGA
			new(StandardResolutions) FIntPoint(1360, 768);
			new(StandardResolutions) FIntPoint(1280, 800);

			new(StandardResolutions) FIntPoint(1152, 864); // XGA+
			new(StandardResolutions) FIntPoint(1440, 900); // WXGA+
			new(StandardResolutions) FIntPoint(1280, 1024); // SXGA
			new(StandardResolutions) FIntPoint(1400, 1050); // SXGA+
			new(StandardResolutions) FIntPoint(1680, 1050); // WSXGA+
			new(StandardResolutions) FIntPoint(1600, 1200); // UXGA
			new(StandardResolutions) FIntPoint(1920, 1200); // WUXGA
		}

		// 四倍扩展图形阵列
		{
			new(StandardResolutions) FIntPoint(2048, 1152); // QWXGA
			new(StandardResolutions) FIntPoint(2048, 1536); // QXGA
			new(StandardResolutions) FIntPoint(2560, 1600); // WQXGA
			new(StandardResolutions) FIntPoint(2560, 2048); // QSXGA
			new(StandardResolutions) FIntPoint(3200, 2048); // WQSXGA
			new(StandardResolutions) FIntPoint(3200, 2400); // QUXGA
			new(StandardResolutions) FIntPoint(3840, 2400); // WQUXGA
		}

		// 超扩展图形阵列
		{
			new(StandardResolutions) FIntPoint(4096, 3072); // HXGA
			new(StandardResolutions) FIntPoint(5120, 3200); // WHXGA
			new(StandardResolutions) FIntPoint(5120, 4096); // HSXGA
			new(StandardResolutions) FIntPoint(6400, 4096); // WHSXGA
			new(StandardResolutions) FIntPoint(6400, 4800); // HUXGA
			new(StandardResolutions) FIntPoint(7680, 4800); // WHUXGA
		}

		// 高清
		{
			new(StandardResolutions) FIntPoint(640, 360); // nHD
			new(StandardResolutions) FIntPoint(960, 540); // qHD
			new(StandardResolutions) FIntPoint(1280, 720); // HD
			new(StandardResolutions) FIntPoint(1920, 1080); // FHD
			new(StandardResolutions) FIntPoint(2560, 1440); // QHD
			new(StandardResolutions) FIntPoint(3200, 1800); // WQXGA+
			new(StandardResolutions) FIntPoint(3840, 2160); // UHD 4K
			new(StandardResolutions) FIntPoint(4096, 2160); // 数字影院倡议4K
			new(StandardResolutions) FIntPoint(7680, 4320); // FUHD
			new(StandardResolutions) FIntPoint(5120, 2160); // UHD 5K
			new(StandardResolutions) FIntPoint(5120, 2880); // UHD+
			new(StandardResolutions) FIntPoint(15360, 8640); // QUHD
		}

		// 按总分辨率大小排序列表
		StandardResolutions.Sort([](const FIntPoint& A, const FIntPoint& B) { return (A.X * A.Y) < (B.X * B.Y); }); // 排序标准分辨率
	}

	// 返回大小约束内的所有标准分辨率
	for (const auto& Resolution : StandardResolutions) // 遍历标准分辨率
	{
		if (Resolution.X >= MinResolution.X && Resolution.Y >= MinResolution.Y && Resolution.X <= MaxResolution.X && Resolution.Y <= MaxResolution.Y) // 如果分辨率在最小和最大范围内
		{
			const float AspectRatio = Resolution.X / (float)Resolution.Y; // 计算宽高比
			if (AspectRatio > MinAspectRatio || FMath::IsNearlyEqual(AspectRatio, MinAspectRatio)) // 如果宽高比大于最小宽高比或接近最小宽高比
			{
				OutResolutions.Add(Resolution); // 添加到输出分辨率
			}
		}
	}
}

/**
 * 获取显示文本
 */
FText ULyraSettingValueDiscrete_Resolution::FScreenResolutionEntry::GetDisplayText() const
{
	if (!OverrideText.IsEmpty()) // 如果覆盖文本不为空
	{
		return OverrideText; // 返回覆盖文本
	}

	FText Aspect = FText::GetEmpty(); // 宽高比文本

	// 预期：4:3=1.333, 16:9=1.777, 16:10=1.6, 多显示器宽屏：>2
	float AspectRatio = (float)Width / (float)Height; // 计算宽高比

	if (FMath::Abs(AspectRatio - (4.0f / 3.0f)) < KINDA_SMALL_NUMBER) // 如果是4:3宽高比
	{
		Aspect = LOCTEXT("AspectRatio-4:3", "4:3"); // 设置4:3文本
	}
	else if (FMath::Abs(AspectRatio - (16.0f / 9.0f)) < KINDA_SMALL_NUMBER) // 如果是16:9宽高比
	{
		Aspect = LOCTEXT("AspectRatio-16:9", "16:9"); // 设置16:9文本
	}
	else if (FMath::Abs(AspectRatio - (16.0f / 10.0f)) < KINDA_SMALL_NUMBER) // 如果是16:10宽高比
	{
		Aspect = LOCTEXT("AspectRatio-16:10", "16:10"); // 设置16:10文本
	}
	else if (FMath::Abs(AspectRatio - (3.0f / 4.0f)) < KINDA_SMALL_NUMBER) // 如果是3:4宽高比
	{
		Aspect = LOCTEXT("AspectRatio-3:4", "3:4"); // 设置3:4文本
	}
	else if (FMath::Abs(AspectRatio - (9.0f / 16.0f)) < KINDA_SMALL_NUMBER) // 如果是9:16宽高比
	{
		Aspect = LOCTEXT("AspectRatio-9:16", "9:16"); // 设置9:16文本
	}
	else if (FMath::Abs(AspectRatio - (10.0f / 16.0f)) < KINDA_SMALL_NUMBER) // 如果是10:16宽高比
	{
		Aspect = LOCTEXT("AspectRatio-10:16", "10:16"); // 设置10:16文本
	}

	FNumberFormattingOptions Options; // 数字格式化选项
	Options.UseGrouping = false; // 不使用分组

	FFormatNamedArguments Args; // 格式化命名参数
	Args.Add(TEXT("X"), FText::AsNumber(Width, &Options)); // 添加宽度参数
	Args.Add(TEXT("Y"), FText::AsNumber(Height, &Options)); // 添加高度参数
	Args.Add(TEXT("AspectRatio"), Aspect); // 添加宽高比参数
	Args.Add(TEXT("RefreshRate"), RefreshRate); // 添加刷新率参数

	return FText::Format(LOCTEXT("AspectRatio", "{X} x {Y}"), Args); // 返回格式化文本
}

#undef LOCTEXT_NAMESPACE